【C++】模板声明与定义不分离

您所在的位置:网站首页 cpp 函数定义 【C++】模板声明与定义不分离

【C++】模板声明与定义不分离

2024-07-15 06:45| 来源: 网络整理| 查看: 265

一般在写C++相关代码的时候,我们总习惯于将类声明和类实现进行分离。也就是说,类的声明一般写在.h文件中,而它的实现一般写在.cpp文件中。但是,在模板类中,这个习惯却要恰恰相反。即:要求模板类的类声明和类实现要都放在头文件,而不能分离。

本文就对模板的这个奇特习惯进行分析。

分离式编译模式

在进行模板特性的讲解之前,首先需要了解一下C++的分离式编译模式。

所谓分离编译模式,就是指:一个程序或者项目由若干个源文件共同实现,而每个源文件单独编译生成目标文件,最后将所有目标文件连接起来形成单一的可执行文件的过程。

C/C++组织源代码和生成可执行文件的方式就是分离式编译模式。

简单粗暴的理解就是,一个C++项目分为若干个cpp文件和h文件,每个cpp文件单独编译成每个的目标文件,最终将每个cpp文件连接在一起组成最后的单一的可执行文件。这里最重要的点就是:编译是相对于每个cpp文件而言的。

接下去的问题就是,对于编译每个cpp文件的时候,是否都需要每个类的实现?

如果都需要每个类的实现,那么就只能将每个类的实现也都写到h文件中,这样在cpp文件中引入的h文件中,才会有每个类的实现;如果不需要每个类的实现,那么就没有必要将每个类的实现写到h文件中。

C/C++所采用的方法是:只要给出类的声明,就可以在本源文件中使用该类。由于每个源文件都是独立的编译单元,在当前源文件中使用但未在此类的实现,就假设在其他的源文件中实现好了。

模板声明与定义 声明定义不分离

但是,分离式编译模式却驯不服模板。

C++标准要求编译器在实例化模板时,必须在上下文中可以查看到其实现;而反过来,在看到实例化模板之前,编译器对模板的实现是不处理的。原因很简单,编译器怎么会预先知道typename实参是什么呢?因此模板的实例化与实现必须放到同一文件中。

《C++编程思想》说明了原因:

模板定义很特殊。由template 处理的任何东西都意味着编译器在当时不为它分配存储空间,它一直处于等待状态直到被一个模板实例告知。在编译器和连接器的某一处,有一机制能去掉指定模板的多重定义。所以为了容易使用,几乎总是在头文件中放置全部的模板声明和定义。

简单来说:只有模板实例化时,编译器才会得知T实参是什么。而编译器在处理模板实例化时,不仅仅要看到模板的定义式,还需要模版的实现体。

为什么需要这样呢?

比如说存在类Rect, 其类定义式写在test.h,类的实现体写在test.cpp中。对于模板来说,编译器在处理test.cpp文件时,编译器无法预知T的实参是什么,所以编译器对其实现是不作处理的。

紧接着在main.cpp中用到了Rect,这个时候会实例化。也就是说,在test.h中会实例出对Rect进行类的声明。但是,由于分离式编译模式,在编译的时候只需要类的声明即可,因此编译是没有任何问题的。

但是在链接的过程中,需要找到Rect的实现部分。但是上面也说了,编译是相对于每个cpp文件而言的。在test.cpp的编译的时候,由于不知道T的实参是什么,并没有对其进行处理。因此,Rect的实现自然并没有被编译,链接也就自然而然地因找不到而出错。

也就是说,模板如果将类声明和类实现进行分离,那么分离式编译模式会导致在链接的时候出现问题。

例子

解释清楚了,接下来可以看一个例子:

在test.h文件中,定义模板类Rect:

#include template class Rect { public: Rect(T l = 0.0f, T t = 0.0f, T r = 0.0f, T b = 0.0f) : left_(l), top_(t), right_(r), bottom_(b) {} void display(); T left_; T top_; T right_; T bottom_; };

在test.cpp文件中,定义模板类Rect方法的实现:

#include "test.h" template void Rect::display() { std::cout


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3